#include "SkyBox.h"

#include <glad/glad.h>
#include <IShaderFormFile.h>
#include <vector>
#include <stdexcept>
#define STB_IMAGE_IMPLEMENTATION
#include <stbImage/stb_image.h>
#include "Singleton.h"
#include "GlobalData.h"

using namespace gl;

class SkyBoxShader : public IShaderFormFile
{
public:
    SkyBoxShader() : IShaderFormFile("shader/skyBox.vert", "shader/skyBox.frag", "SkyBoxShader") { }
};

struct SkyBoxImpl
{
    uint32_t textureId = 0;
    IShader* shader = nullptr;
    uint32_t VAO = 0;
    uint32_t VBO = 0;
    uint32_t EBO = 0;

    ~SkyBoxImpl()
    {
        delete shader;
    }

    void LoadShader()
    {
        shader = new SkyBoxShader();
        shader->UseShader();
        shader->setMat4("projMatrix", Singleton<GlobalData>::Instance().ProjMatrix);
    }
    void LoadCubeMapTexture(const std::string &path)
    {
        glGenTextures(1, &textureId);
        glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);

        std::vector<std::string> faces
                {
                        path + "/right.jpg", // right
                        path + "/left.jpg", // left
                        path + "/top.jpg", // top
                        path + "/bottom.jpg", // bottom
                        path + "/front.jpg", // front
                        path + "/back.jpg" // back
                };

        int width, height, nrChannels;
        for (unsigned int i = 0; i < faces.size(); i++)
        {
            uint8_t* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);

            if (data == nullptr)
            {
                stbi_image_free(data);
                throw std::runtime_error(std::string("Cubemap texture failed to load at path: ") + faces[i]);
            }

            GLint dataFormat = 0;
            GLenum format = 0;
            switch (nrChannels)
            {
                case 3:
                    dataFormat = GL_RGB;
                    format = GL_RGB; break;
                case 4:
                    dataFormat = GL_RGBA;
                    format = GL_RGBA; break;
                default:
                    break;
            }

            if (format == 0)
            {
                stbi_image_free(data);
                throw std::runtime_error(std::string("Cubemap texture failed to load at path: ") + faces[i]);
            }

            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, dataFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
            stbi_image_free(data);
        }

        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    }
    void CreateVAO()
    {
        // 每个顶点由8个float组成, 由332分为三个属性, 分别为画布位置xyz, 颜色值rgb, 纹理采样位置st
        float vertices[] = {
                100.0f,  100.0f, 100.0f,
                100.0f, -100.0f, 100.0f,
                -100.0f, -100.0f, 100.0f,
                -100.0f,  100.0f, 100.0f,

                100.0f,  100.0f, -100.0f,
                100.0f, -100.0f, -100.0f,
                -100.0f, -100.0f, -100.0f,
                -100.0f,  100.0f, -100.0f,

                100.0f,  100.0f, -100.0f,
                100.0f,  100.0f, 100.0f,
                -100.0f, 100.0f, 100.0f,
                -100.0f, 100.0f, -100.0f,

                100.0f,  -100.0f, -100.0f,
                100.0f,  -100.0f, 100.0f,
                -100.0f, -100.0f, 100.0f,
                -100.0f, -100.0f, -100.0f,

                -100.0f,  100.0f, -100.0f,
                -100.0f,  100.0f, 100.0f,
                -100.0f, -100.0f, 100.0f,
                -100.0f, -100.0f, -100.0f,

                100.0f,  100.0f, -100.0f,
                100.0f,  100.0f, 100.0f,
                100.0f, -100.0f, 100.0f,
                100.0f, -100.0f, -100.0f,
        };
        unsigned int indices[] = {
                0, 1, 3, // 第一个三角形
                1, 2, 3, // 第二个三角形

                4, 5, 7, // 第一个三角形
                5, 6, 7,  // 第二个三角形

                8, 9, 11, // 第一个三角形
                9, 10, 11,  // 第二个三角形

                12, 13, 15, // 第一个三角形
                13, 14, 15,  // 第二个三角形

                16, 17, 19, // 第一个三角形
                17, 18, 19,  // 第二个三角形

                20, 21, 23, // 第一个三角形
                21, 22, 23,  // 第二个三角形
        };

        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);

        glBindVertexArray(VAO);

        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)nullptr);
        glEnableVertexAttribArray(0);

        glBindVertexArray(0);
    }
};

SkyBox::SkyBox(const std::string &path)
{
    impl = new SkyBoxImpl();

    impl->LoadShader();
    impl->LoadCubeMapTexture(path);
    impl->CreateVAO();
}

SkyBox::~SkyBox()
{
    delete impl;
}

void SkyBox::Render()
{
    glDepthMask(GL_FALSE);
    glDisable(GL_DEPTH_TEST);
    impl->shader->UseShader();
    impl->shader->setMat4("viewMatrix", glm::mat4(glm::mat3(Singleton<GlobalData>::Instance().ViewMatrix)));
    glBindVertexArray(impl->VAO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, impl->textureId);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, nullptr);
    glBindVertexArray(0);
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
}

